package com.sunday.minio.autoconfiguration;


import com.sunday.minio.core.MinioTemplate;
import io.minio.MinioClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;

import java.util.Optional;

import static com.sunday.minio.constants.MinioConstants.EMPTY;

/**
 * Minio Handler
 * factoryBean.setPrimary(true) 是用于设置 Spring 中的 FactoryBean 是否为首选 Bean 的属性。
 * 在 Spring 中，FactoryBean 是一个特殊的 Bean，它不是直接暴露自身的实例，而是通过 getObject() 方法创建和返回其他对象的实例。当应用程序需要获取该对象时，可以使用 FactoryBean 的名称获取到由其创建的对象实例。
 * 通过调用 setPrimary(true) 方法，我们可以将 FactoryBean 标记为首选 Bean。当存在多个候选的 Bean 实例时，首选 Bean 会被优先选择作为依赖注入的目标。如果没有明确指定首选 Bean，则 Spring 可能会抛出异常或根据其它规则进行解析。
 * 通过设置 FactoryBean 的 primary 属性为 true，我们可以确保该 FactoryBean 在注入依赖时被优先选择。这对于解决具有相同类型的多个 Bean 候选项时非常有用，以确保正确的 Bean 被注入到目标组件中。
 * 需要注意的是，setPrimary(true) 方法只能应用于实现了 FactoryBean 接口的类的实例。对于普通的 Bean（非 FactoryBean），无需设置 primary 属性。
 *
 * @author zsy
 * @see com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration 参考
 * @see com.alibaba.cloud.sentinel.custom.SentinelDataSourceHandler 参考
 * @see org.apache.dubbo.spring.boot.autoconfigure.DubboConfigurationProperties 参考
 * @see org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration 参考
 * @since 2023/10/18
 */
@Slf4j
@RequiredArgsConstructor
public class MinioDataSourceHandler implements InitializingBean {

    private final DefaultListableBeanFactory beanFactory;

    private final MinioConfigurationProperties minioProperties;

    private boolean primary;

    @Override
    public void afterPropertiesSet() {
        Optional.ofNullable(minioProperties.getRegistry())
                .ifPresent(minioProperties -> registerBean(EMPTY, minioProperties));
        minioProperties.getRegistries()
                .forEach((prefix, minioProperties) -> registerBean(prefix, minioProperties));
    }

    private void registerBean(String prefix, final MinioProperties properties) {
        BeanDefinitionBuilder minioClientBuilder = parseBeanDefinition(properties);
        String minioClientName = lowercase(prefix + MinioClient.class.getSimpleName());
        this.beanFactory.registerBeanDefinition(
                minioClientName,
                minioClientBuilder.getBeanDefinition());
        MinioClient minioClient = this.beanFactory.getBean(minioClientName, MinioClient.class);
        BeanDefinitionBuilder minioTemplateBuilder = parseBeanDefinition(properties, minioClient);
        this.beanFactory.registerBeanDefinition(
                lowercase(prefix + MinioTemplate.class.getSimpleName()),
                minioTemplateBuilder.getBeanDefinition());
    }

    protected BeanDefinitionBuilder parseBeanDefinition(final MinioProperties properties,
                                                        final MinioClient minioClient) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(
                MinioTemplate.class,
                () -> new MinioTemplate(properties, minioClient));
        if (!primary) {
            // 方法返回一个 AbstractBeanDefinition 对象，这个对象代表了 Spring IoC 容器中的 bean 定义。
            // AbstractBeanDefinition 创建的是由对应的 FactoryBean 实现创建的 Bean，通过调用 FactoryBean 的 getObject() 方法创建。
            AbstractBeanDefinition factoryBean = builder.getRawBeanDefinition();
            // factoryBean.setPrimary(true) 是用于设置 Spring 中的 FactoryBean 是否为首选 Bean 的属性。
            factoryBean.setPrimary(primary = !primary);
        }
        return builder;
    }

    protected BeanDefinitionBuilder parseBeanDefinition(final MinioProperties properties) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MinioClient.class, () ->
                MinioClient.builder()
                        .endpoint(properties.getEndpoint())
                        .credentials(properties.getAccessKey(), properties.getSecretKey())
                        .build());
        return builder;
    }

    protected String lowercase(String str) {
        return str.substring(0, 1).toLowerCase() + str.substring(1);
    }
}
